#ifndef Matrice4_h
#define Matrice4_h

/**
@brief gestion Matrice Homogène 4D de transformation\n
 matrix is : \n
c[0] c[4] c[8] c[12] \n
c[1] c[5] c[9] c[13] \n
c[2] c[6] c[10] c[14] \n
c[3] c[7] c[11] c[15] \n

@author Bourgès Cyril
@date 8 juillet 2009
@date 24 octobre 2009
@date 14 octobre 2011
@version 1.6
@see Site Auteur : http://shadowluz.alwaysdata.net/ \n
*/

//---STD-------------------------------------------------------------
#include <iostream>
#include <cmath>

//---SM--------------------------------------------------------------
#include "Matrice.hpp"

//-------------------------------------------------------------------

namespace SM {

template<class T>
class Matrice4 : public Matrice<T> {

public:
  ///Enum définissant les axe pour les rotation 
  enum eAxe {
    AXE_X,
    AXE_Y,
    AXE_Z
  };

  /**
  * @brief Matrice 4x4
  * @param _identite : Si true génère une matrice identité sinon case à 0
  */
  Matrice4 (bool _identite=false) : Matrice<T>(4, 4, _identite)  {
  }

  Matrice4 (const Matrice4<T> &_copie) : Matrice<T>(_copie){
  }

  ~Matrice4() {
  }

  /**
  * @brief Tranforme une matrice 3x3 ou 3x1 ou 1x3 en version Homogène 4x4
  */
  void setMatrice (Matrice<T> *_matrice) {
    this->initIdentite() ;
    if (_matrice->getWidth()==3 && _matrice->getHeight()==3) {
      for (unsigned int i = 0 ; i < _matrice->getWidth() ; ++i) {
        for (unsigned int j = 0 ; j < _matrice->getHeight() ; ++j) {
          this->data[i+j*this->width] = _matrice->getData(i+j*_matrice->getWidth()) ;
        }
      }
    }else if ((_matrice->getWidth()==3 && _matrice->getHeight()==1) ||
              (_matrice->getWidth()==1 && _matrice->getHeight()==3)) {
      for (int i = 0 ; i < 3 ; ++i) {
          this->data[(this->width-1)+i*this->width] = _matrice->getData(i) ;
      }
    }
  }

  /**
  * @brief Ajoute la matrice translation a la matrice courante
  * @param _x : Déplacement sur l'axe X
  * @param _y : Déplacement sur l'axe Y
  * @param _z : Déplacement sur l'axe Z
  */
  void addTranslation (T _x, T _y, T _z) {
    Matrice4<T> matDeplacement = matriceTranslation (_x, _y, _z) ;
    Matrice<T> *retour = this ;
    (*retour)= matDeplacement * (*this) ;
  }

  /**
  * @brief Génère une matrice translation
  * @param _x : Déplacement sur l'axe X
  * @param _y : Déplacement sur l'axe Y
  * @param _z : Déplacement sur l'axe Z
  * @return Matrice Translation
  */
  Matrice4<T> matriceTranslation (T _x, T _y, T _z) {
    Matrice4<T> retour (true) ;
    retour.data[12] = _x ;
    retour.data[13] = _y ;
    retour.data[14] = _z ;
    return retour ;
  }

  /**
  * @brief Ajoute la matrice rotation a la matrice courante
  * @param _angle : Angle de rotation
  * @param _axe : Axe sur lequels on fait  la rotation
  */
  void addRotation (double _angle, eAxe _axe) {
    Matrice4<T> matDeplacement =  matriceRotation (_angle, _axe) ;
    Matrice<T> *retour = this ;
    (*retour) = matDeplacement * (*this) ;
  }

  /**
  * @brief Génère une matrice rotation
  * @param _angle : Angle de rotation
  * @param _axe : Axe sur lequels on fait  la rotation
  * @return Matrice Rotation
  */
  Matrice4<T> matriceRotation (double _angle, eAxe _axe) {
    Matrice4<T> retour (true) ;
    double cosinus = cos(_angle),
          sinus = sin (_angle) ;
    switch (_axe) {
      case AXE_X:
        retour.data[5] = cosinus ;
        retour.data[9] = -sinus ;
        retour.data[6] = sinus ;
        retour.data[10] = cosinus ;
      break ;
      case AXE_Y:
        retour.data[10] = cosinus ;
        retour.data[2] = -sinus ;
        retour.data[8] = sinus ;
        retour.data[0] = cosinus ;
      break ;
      case AXE_Z:
        retour.data[0] = cosinus ;
        retour.data[4] = -sinus ;
        retour.data[1] = sinus ;
        retour.data[5] = cosinus ;
      break ;
    }
    return retour ;
  }

  /**
  * @brief Ajoute une matrice d'homothétie à la matrice courante
  * @param _valeur : coefficent de transformation
  */
  void addHomothetie (T _valeur) {
    Matrice4<T> matDeplacement =  matriceHomothetie (_valeur) ;
    Matrice<T> *retour = this ;
    (*retour) = matDeplacement * (*this) ;
  }

  /**
  * @brief Génère une matrice d'homothétie
  * @param _valeur : coefficent de transformation
  * @return Matrice d'Homothétie
  */
  Matrice4<T> matriceHomothetie(T _valeur) {
    Matrice4<T> retour  (true) ;
    for (unsigned int i = 0 ; i < (this->width-1) ; ++i) {
      retour.data[i*this->width+i] = _valeur ;
    }
    return retour ;
  }

  void matriceInverse3x3(Matrice<T> &_matrice) {
    Matrice<T> retour  (3, 3, false) ;
    Matrice<T> tmp  (3, 3, false) ;
    //Copie matrice 3x3 haut - gauche dans retour ;
    for (unsigned int i = 0 ; i < 3 ; ++i) {
      for (unsigned int j = 0 ; j < 3 ; ++j) {
        retour.getData()[i+j*3] =this->getData()[i+j*this->getWidth()] ;
        tmp.getData()[i+j*3] =retour.getData()[i+j*3] ;
      }
    }

    //Calcul inverse d'après le site : http://fr.wikipedia.org/wiki/Matrice_inversible
    float fdeterminant = retour.det() ;
    fdeterminant = 1.0f / fdeterminant ;
    retour.getData()[0] = fdeterminant*( tmp.getData()[4] * tmp.getData()[8] - tmp.getData()[5] * tmp.getData()[7]) ;
    retour.getData()[1] = fdeterminant*( tmp.getData()[2] * tmp.getData()[7] - tmp.getData()[1] * tmp.getData()[8]) ;
    retour.getData()[2] = fdeterminant*( tmp.getData()[1] * tmp.getData()[5] - tmp.getData()[2] * tmp.getData()[4]) ;

    retour.getData()[3] = fdeterminant*( tmp.getData()[5] * tmp.getData()[6] - tmp.getData()[3] * tmp.getData()[8]) ;
    retour.getData()[4] = fdeterminant*( tmp.getData()[0] * tmp.getData()[8] - tmp.getData()[2] * tmp.getData()[6]) ;
    retour.getData()[5] = fdeterminant*( tmp.getData()[2] * tmp.getData()[3] - tmp.getData()[0] * tmp.getData()[5]) ;

    retour.getData()[6] = fdeterminant*( tmp.getData()[3] * tmp.getData()[7] - tmp.getData()[4] * tmp.getData()[6]) ;
    retour.getData()[7] = fdeterminant*( tmp.getData()[1] * tmp.getData()[6] - tmp.getData()[0] * tmp.getData()[7]) ;
    retour.getData()[8] = fdeterminant*( tmp.getData()[0] * tmp.getData()[4] - tmp.getData()[1] * tmp.getData()[3]) ;
    _matrice = retour ;
  }
  
  /*
  void operator= (Matrice<T> &_matrice) {
    T** ppData = this->getPointeurData() ;
    delete[] (*ppData) ;
    this->setWidth(_matrice.getWidth()) ;
    this->setHeight(_matrice.getHeight()) ;
    unsigned int taille = this->getWidth()*this->getHeight() ;
    (*ppData) = new T [taille] ;

    for (unsigned int i = 0 ; i < taille ; ++i) {
      (*ppData)[i] = _matrice.getData(i) ;
    }
  }
  */
};  //---Matrice4
}   //---namespace SluzMatrice
#endif


